package nl.infodation.emr.pricingservice.dao.impl;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.PersistenceException;
import javax.persistence.Query;

import nl.infodation.emr.pricingservice.controller.AbstractBaseController;
import nl.infodation.emr.pricingservice.dao.DAO;
import nl.infodation.emr.utility.Constants;
import nl.infodation.emr.utility.EntityManagerFactoryUtil;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.googlecode.genericdao.dao.jpa.GenericDAOImpl;
import com.googlecode.genericdao.search.jpa.JPAAnnotationMetadataUtil;
import com.googlecode.genericdao.search.jpa.JPASearchProcessor;

public class DAOImpl<T, ID extends Serializable> extends GenericDAOImpl<T, Serializable> implements DAO<T, ID> {

	protected static final Logger LOG = LoggerFactory
			.getLogger(AbstractBaseController.class);
	protected EntityManagerFactory EM_FACTORY = EntityManagerFactoryUtil
			.getEntityManagerFactory(Constants.PRICING_PERSISTENCE_UNIT);
	
	protected DAOImpl() {
		persistentClass = getEntityClass();
	}

	protected EntityManager createEntityManager(EntityManagerFactory EntityManagerFactory) {
		return EntityManagerFactory.createEntityManager();
	}

	@SuppressWarnings("unchecked")
	@Override
	public List<T> findEntitiesByIDs(List<ID> ids) {
		List<T> result = new ArrayList<>();
		
		if (ids != null && ids.size() > 0) {
			EntityManager entityManager = createEntityManager(EM_FACTORY);
			
			Query query = entityManager.createQuery("SELECT p FROM " + getSimpleName() + " p WHERE p." + getIdentifyName(entityManager) + " IN :ids");
			query.setParameter("ids", ids);
			result = query.getResultList();
		}
		
		return result;
	}
	
	/* (non-Javadoc)
	 * @see com.googlecode.genericdao.dao.jpa.GenericDAOImpl#findAll()
	 */
	@SuppressWarnings("unchecked")
	@Override
	public List<T> findAll() {
		List<T> result = new ArrayList<>();
		EntityManager entityManager = createEntityManager(EM_FACTORY);
		Query query = entityManager.createQuery("SELECT p FROM " + getSimpleName() + " p");
		result = query.getResultList();
		return result;
	}

	/* (non-Javadoc)
	 * @see nl.infodation.emr.pricingservice.dao.DAO#remove(java.util.List)
	 */
	@Override
	public Boolean remove(List<T> entities) {
		boolean saved = Boolean.FALSE;
		
		if (entities == null || entities.size() == 0) {
			return Boolean.FALSE;
		}
		EntityManager entityManager = createEntityManager(EM_FACTORY);
		super.setEntityManager(entityManager);
		super.setSearchProcessor(new JPASearchProcessor(new JPAAnnotationMetadataUtil()));
		try {
			super.em().getTransaction().begin();
				Iterator<T> itorator = entities.iterator();
				while (itorator.hasNext()) {
					T entity = itorator.next();
					Serializable id = super.getMetadataUtil().getId(entity);
					super.removeById(id);
				}
			super.em().getTransaction().commit();
			saved = Boolean.TRUE;
		} catch (IllegalStateException | PersistenceException ex) {
			LOG.error("Can't remove '" + super.persistentClass.getName() + "' entities with SIZE = {" + entities.size() + "}");
			rollback(super.em());
		} finally {
			close(super.em());
		}
		return saved;
	}

	@Override
	public Boolean deleteById(ID id) {
		if (null == id) {
			return Boolean.FALSE;
		}
		boolean removed = Boolean.FALSE;
		EntityManager entityManager = createEntityManager(EM_FACTORY);
		super.setEntityManager(entityManager);
		super.setSearchProcessor(new JPASearchProcessor(new JPAAnnotationMetadataUtil()));
		T entity = super.find(id);
		try {
			super.em().getTransaction().begin();
				super.remove(entity);
			super.em().getTransaction().commit();
		} catch (IllegalStateException | PersistenceException ex) {
			LOG.error("Can't delete '" + super.persistentClass.getName() + "' entity with id = {" + id + "}");
			rollback(super.em());
		} finally {
			close(super.em());
		}
		return removed;
	}

	@SuppressWarnings("unchecked")
	@Override
	public Boolean delete(T t) {
		if (null == t) {
			return false;
		}
		Serializable id = super.getMetadataUtil().getId(t);
		return deleteById((ID)id);
	}
	
	/* (non-Javadoc)
	 * @see nl.infodation.emr.pricingservice.controller.IController#save(java.util.List)
	 */
	@Override
	public Boolean save(List<T> entities) {
		boolean saved = Boolean.FALSE;
		
		if (entities == null || entities.size() == 0) {
			return Boolean.FALSE;
		}
		
		EntityManager entityManager = createEntityManager(EM_FACTORY);
		super.setEntityManager(entityManager);
		super.setSearchProcessor(new JPASearchProcessor(new JPAAnnotationMetadataUtil()));
		try {
			super.em().getTransaction().begin();
				Iterator<T> itorator = entities.iterator();
				while (itorator.hasNext()) {
					super.save(itorator.next());
				}
			super.em().getTransaction().commit();
			saved = Boolean.TRUE;
		} catch (Exception ex) { // IllegalStateException | PersistenceException
			LOG.error("Can't save '" + super.persistentClass.getName() + "' entities with SIZE = {" + entities.size() + "}");
			rollback(super.em());
		} finally {
			close(super.em());
		}
		
		return saved;
	}
	
	@Override
	public T save(T entity) {
		EntityManager entityManager = createEntityManager(EM_FACTORY);
		super.setEntityManager(entityManager);
		super.setSearchProcessor(new JPASearchProcessor(new JPAAnnotationMetadataUtil()));
		try {
			super.em().getTransaction().begin();
				entity = super.save(entity);
			super.em().getTransaction().commit();
		} catch (IllegalStateException | PersistenceException ex) {
			Serializable id = super.getMetadataUtil().getId(entity);
			LOG.error("Can't save '" + super.persistentClass.getName() + "' entity with id = {" + id + "}");
			rollback(super.em());
		} finally {
			close(super.em());
		}
		return entity;
	}

	private String getSimpleName() {
		return super.persistentClass.getSimpleName();
	}

	@SuppressWarnings("unchecked")
	private String getIdentifyName(EntityManager entityManager) {
		Class<ID> idClass = ((Class<ID>) ((ParameterizedType) getClass()
				.getGenericSuperclass()).getActualTypeArguments()[1]);
		return entityManager.getMetamodel().entity(getEntityClass()).getId(idClass).getName();
	}

	@SuppressWarnings("unchecked")
	private Class<T> getEntityClass() {
		return ((Class<T>) ((ParameterizedType) getClass()
				.getGenericSuperclass()).getActualTypeArguments()[0]);
	}

	protected void rollback(EntityManager entityManager) {
		if (entityManager != null && entityManager.getTransaction() != null) {
			entityManager.getTransaction().rollback();
		}
	}
	
	protected void close(EntityManager entityManager) {
		if (entityManager != null && entityManager.isOpen()) {
			entityManager.close();
		}
	}
	
	protected String like(String value) {
		if (value == null) {
			return "%%";
		} else {
			StringBuilder strBuilder = new StringBuilder();
			strBuilder.append("%");
			strBuilder.append(value);
			strBuilder.append("%");
			return strBuilder.toString();
		}
	}
	
	protected void likeOrEmpty(Query query, String key, String value) {
		if (StringUtils.isNotBlank(value)) {
			query.setParameter(key, like(value));
		} else {
			query.setParameter(key, like(value));				
		}
	}

	protected void equalsOrEmpty(Query query, String key, String value) {
		if (StringUtils.isNotBlank(value)) {
			query.setParameter(key, value);
		} else {
			query.setParameter(key, like(value));				
		}
	}
	
	protected String getStrValue(Object value) {
		if (value == null) {
			return "";
		} else {
			return String.valueOf(value);
		}
	}

	@Override
	public T get(ID id) {
		if (null == id) {
			return null;
		}
		T entity = null;
		
		EntityManager entityManager = createEntityManager(EM_FACTORY);
		super.setEntityManager(entityManager);
		try {
			entity = super.find(id);
		} finally {
			close(super.em());
		}
		return entity;
	}
	
}
